//
// Created by monoliths on 8/30/22.
//

#include "HttpStreamReceive.h"
#include <cassert>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <unistd.h>

void HttpStreamReceive::setPartReceivedCallback(const std::function<bool(char *, size_t)>& callback_) {
    this->m_partReceivedCallback = callback_;
}

std::function<bool(char *, size_t)> &&HttpStreamReceive::partReceivedCallback() {
    return std::move(this->m_partReceivedCallback);
}

int HttpStreamReceive::readLineFromSocket(int fd, char* recvData, int recvLength) const {
    char tmp;
    int len = 0;
    int ret;
    while(len < recvLength && (ret = recv(fd,&tmp,1,0)) == 1)
    {
        if(tmp == '\r')
            break;
        memcpy(recvData + len,&tmp,1);
        len += 1;
    }
    if(ret != 1)
    {
        std::cout << "readline err" << errno << strerror(errno) << std::endl;
        return -1;
    }
    if(len == recvLength)
        return len;
    if(tmp == '\r')
    {
        ret = recv(this->m_socketFD,&tmp,1,0);
        if(ret != 1)
        {
            std::cout << "readline err" << errno << strerror(errno) << std::endl;
            return -1;
        }
        else
        {
            return len;
        }
    }
//    return len;
}

int HttpStreamReceive::readBytesFromSocket(int fd, char *recvData, int recvLength) const{

    int len = 0,l = 0;

    while((len = recv(fd,recvData + l,recvLength - l,0)) > 0)
    {
        l += len;
    }

    if(len < 0)
        return len;
    return l;
}


void HttpStreamReceive::startRequestStream() {
    assert(this->m_host != "0.0.0.0");
//    assert(!this->partReceivedCallback());

//    sprintf(sendData,"GET / HTTP/1.1\r\nAccept: */*\r\nAccept-Language: zh-Hans-CN, zh-Hans; q=0.8, en-US; q=0.5, en; q=0.3\r\nConnection: close\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134\r\n\r\n",host);

    struct hostent *phost = nullptr;
    struct sockaddr_in sin{};
    char tmp[0x8000 + 1];

//    char *tmp = new char[0x8000 + 1];
    int tmp_len = 0;
    char name[100], value[100], *index1, *index2;

    int recv_len = 0, len, len2;

    try {
        phost = gethostbyname(this->m_host.c_str());


        if (phost == nullptr) {
            throw std::runtime_error("net work error!");
        }

        this->m_socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        sin.sin_family = AF_INET;
        sin.sin_port = htons(this->port());
        sin.sin_addr = *(struct in_addr *) phost->h_addr_list[0];

        if (connect(this->m_socketFD, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
            std::string errMessage("connect error");
            errMessage.append(strerror(errno));
            throw std::runtime_error(errMessage);
        }

        std::cout << "connect successful" << std::endl;

        send(this->m_socketFD, requestHeader().c_str(), requestHeader().length(), 0);

        memset(tmp, 0, sizeof(tmp));
        tmp_len = this->readLineFromSocket(this->m_socketFD, tmp, sizeof(tmp));

        if (tmp_len == -1) {
            throw std::runtime_error("request not response message!");
        }

        if (memcmp(tmp, "HTTP/1.1 200", 12) != 0) {
//            std::cout << "response code: 200" << std::endl;
//            return;
        }

        do {
            memset(tmp, 0, sizeof(tmp));

            tmp_len = this->readLineFromSocket(this->m_socketFD, tmp, sizeof(tmp));

            if (tmp_len <= 0) {
                break;
            }

            memset(name, 0, sizeof(name));
            memset(value, 0, sizeof(value));

            index1 = strchr(tmp, ':');

            if (index1 == nullptr) {
                break;
            }

            strncpy(name, tmp, index1 - tmp);

            if (*(index1 + 1) == ' ') {
                index1 += 2;
            } else {
                index1 += 1;
            }
            strcpy(value, index1);

            setenv(name, value, 1);
        } while (true);

        if (tmp_len < 0) {
            throw std::runtime_error("read no data!");
        }

        index1 = nullptr;
        index1 = getenv("Transfer-Encoding");
        if (index1 != nullptr && strcmp(index1, "chunked") == 0) {
            recv_len = 0;
            while (true) {
                memset(tmp, 0, sizeof(tmp));
                tmp_len = this->readLineFromSocket(this->m_socketFD, tmp, sizeof(tmp));
                if (tmp_len == 0) {
                    throw std::runtime_error("transfer encoding read no data!");
                }
                len = 0;
                sscanf(tmp, "%x", &len);

                if (len == 0) {
                    std::cout << "receive finish!" << std::endl;
                    break;
                }
                memset(tmp, 0, sizeof(tmp));
                tmp_len = 0;
                tmp_len = this->readBytesFromSocket(this->m_socketFD, tmp, len);
                if (tmp_len != len || tmp_len < 0) {
//                    throw std::runtime_error("read data error!");
                } else {
                    if (!(this->partReceivedCallback()(tmp, len)))
                    {
                        std::cout << "interrupted by callback" << std::endl;
                        return;
                    }
                }

                tmp_len = this->readLineFromSocket(this->m_socketFD, tmp, sizeof(tmp));
                if (tmp_len == 0)
                {
                    continue;
                }
                else
                {
                    break;
                }
            }
        }

        index1 = nullptr;
        index1 = getenv("Content-Length");
        if (index1 != nullptr) {
            len = atol(index1);
            recv_len = 0;
            while (recv_len < len) {
                memset(tmp, 0, sizeof(tmp));
                auto length_ = sizeof(tmp) - 1;
                if ((len - recv_len) < (sizeof(tmp) - 1)) {
                    length_ = (len - recv_len);
                }
                tmp_len = this->readBytesFromSocket(this->m_socketFD, tmp, length_);
                if (tmp_len < 0) {
                    throw std::runtime_error("read content length error!");
                }
                recv_len += tmp_len;
            }
        }
    } catch (const std::exception &e) {
        std::cout << e.what() << std::endl;
    }

    close(this->m_socketFD);
}

const std::string &HttpStreamReceive::requestHeader(){
    return std::move(this->m_requestHeader);
}

void HttpStreamReceive::setRequestHeader(std::string&& header_) {
    this->m_requestHeader = header_;
}

uint16_t& HttpStreamReceive::port() {
    return this->m_port;
}

void HttpStreamReceive::setPort(uint16_t port_) {
    this->m_port = port_;
}

//
//HttpStreamReceive::HttpStreamReceive(std::string host_, uint16_t) {
//
//}

